Aprendizaje no-supervisada¶

Profesor: Anthony D. Cho

Ayudante: Esnil Guevara

Asunto: Clustering de canciones de Spotify

Evaluación: Tarea 02


Estudiantes:

  • DAD: 18.634.930-K, Sebastian Cantergiani

Instrucciones¶

  • La actividad puede ser realizada en grupo de hasta 3 personas.
  • Por favor responder en este mismo notebook.
  • Renombrar el archivo agregando el apellido de las y los integrantes, por ejemplo Tarea_02_Akina_Nosta.ipynb
  • Una vez completada la tarea, uno de los integrantes debe subir al enlace habilitada en el Webcursos.
  • Fecha de publicación: 07 de octubre de 2023
  • Fecha de entrega: Hasta el domingo 15 de octubre de 2023, 23:59 hrs.

Objetivo de la tarea¶

Aplicar algunas de las técnicas aprendidas de Clustering para crear listas de reproducción en Spotify

Se va a considerar en la evaluación:

  • Pre-procesamiento de los datos.
  • Estrategia para encontrar el mejor clustering.
  • Interpretación de los clusteres formados.
  • Construcción del playlist

Dataset:¶

Enlace para la descarga de la data music.csv y colocarlo junto con el script, con eso debería funcionar sin ningún problema.

Lista de librerias¶

In [ ]:
import pandas as pd
import numpy as np
from tqdm import tqdm
from itertools import product

## Agregar las librerias que iran a usar
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.decomposition import PCA
from pca import pca

#Clustering
from sklearn.cluster import KMeans
from fcmeans import FCM
from sklearn.mixture import GaussianMixture
from sklearn.cluster import DBSCAN
from sklearn.neighbors import NearestNeighbors

#Outliers
from sklearn.neighbors import LocalOutlierFactor
from sklearn.ensemble import IsolationForest
from sklearn.svm import OneClassSVM

#Metrics
from sklearn.metrics import silhouette_score, davies_bouldin_score

#Librerias para trabajar texto
from string import punctuation
from unidecode import unidecode

# Mute all future warnings
import warnings
warnings.filterwarnings("ignore")

Carga de datos¶

In [ ]:
dataset = pd.read_csv("musics.csv")
dataset
Out[ ]:
artist_name artist_id album_id album_type album_release_date album_release_year album_release_date_precision danceability energy key ... track_name track_preview_url track_number type track_uri external_urls.spotify album_name key_name mode_name key_mode
0 2Pac 1ZwdS5xdxEREPySFridCfh 1nGbXgS6toEOcFCDwEl5R3 album 2019-08-01 2019.0 day 0.656 0.882 0 ... California Love https://p.scdn.co/mp3-preview/93e456ef0b73f23f... 1 track spotify:track:6ayeqYtOtwVhqVB6k6MKoh https://open.spotify.com/track/6ayeqYtOtwVhqVB... California Love C major C major
1 2Pac 1ZwdS5xdxEREPySFridCfh 1nGbXgS6toEOcFCDwEl5R3 album 2019-08-01 2019.0 day 0.810 0.642 8 ... Slippin' Into Darkness https://p.scdn.co/mp3-preview/440595604d3f4946... 2 track spotify:track:1UDsnzBp8gUCFsrzUDlZI9 https://open.spotify.com/track/1UDsnzBp8gUCFsr... California Love G# major G# major
2 2Pac 1ZwdS5xdxEREPySFridCfh 1nGbXgS6toEOcFCDwEl5R3 album 2019-08-01 2019.0 day 0.548 0.590 4 ... Ride or Die https://p.scdn.co/mp3-preview/cc18dc90d609d375... 3 track spotify:track:3bKs15o7F9VP6GBExCbb6H https://open.spotify.com/track/3bKs15o7F9VP6GB... California Love E minor E minor
3 2Pac 1ZwdS5xdxEREPySFridCfh 1nGbXgS6toEOcFCDwEl5R3 album 2019-08-01 2019.0 day 0.839 0.657 5 ... I Ain't Mad At Cha https://p.scdn.co/mp3-preview/d138f0170423cd9a... 4 track spotify:track:4L0iAst3yLonw8aGxTRCvb https://open.spotify.com/track/4L0iAst3yLonw8a... California Love F minor F minor
4 2Pac 1ZwdS5xdxEREPySFridCfh 1nGbXgS6toEOcFCDwEl5R3 album 2019-08-01 2019.0 day 0.854 0.694 0 ... Static II https://p.scdn.co/mp3-preview/dddb7d0ea0205338... 5 track spotify:track:66men3J5qFERvIY06M5hQ9 https://open.spotify.com/track/66men3J5qFERvIY... California Love C minor C minor
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
447617 ZZ Top 2AM4ilv6UzW0uMRuqKtDgN 0Y9jM9umdMOH7t19urnOw4 album 1970-01-16 1970.0 day 0.673 0.520 0 ... Neighbor, Neighbor https://p.scdn.co/mp3-preview/530e4ce805075d4e... 6 track spotify:track:4snfGOJDRVfY9jH43CsmJB https://open.spotify.com/track/4snfGOJDRVfY9jH... ZZ Top's First Album C major C major
447618 ZZ Top 2AM4ilv6UzW0uMRuqKtDgN 0Y9jM9umdMOH7t19urnOw4 album 1970-01-16 1970.0 day 0.674 0.397 4 ... Certified Blues https://p.scdn.co/mp3-preview/ee79a42629975bd9... 7 track spotify:track:7mdPXhJBfxJBonEmqZvm9t https://open.spotify.com/track/7mdPXhJBfxJBonE... ZZ Top's First Album E minor E minor
447619 ZZ Top 2AM4ilv6UzW0uMRuqKtDgN 0Y9jM9umdMOH7t19urnOw4 album 1970-01-16 1970.0 day 0.466 0.406 7 ... Bedroom Thang https://p.scdn.co/mp3-preview/dd0df120d0b10f80... 8 track spotify:track:23dC4zCpB1bnLzBxAXDLD7 https://open.spotify.com/track/23dC4zCpB1bnLzB... ZZ Top's First Album G major G major
447620 ZZ Top 2AM4ilv6UzW0uMRuqKtDgN 0Y9jM9umdMOH7t19urnOw4 album 1970-01-16 1970.0 day 0.611 0.337 0 ... Just Got Back from Baby's https://p.scdn.co/mp3-preview/5fa536adf7c6117e... 9 track spotify:track:7AX7qVqFljHOJC3FIQ8a6t https://open.spotify.com/track/7AX7qVqFljHOJC3... ZZ Top's First Album C major C major
447621 ZZ Top 2AM4ilv6UzW0uMRuqKtDgN 0Y9jM9umdMOH7t19urnOw4 album 1970-01-16 1970.0 day 0.635 0.645 4 ... Backdoor Love Affair https://p.scdn.co/mp3-preview/311f47b27f09ff6e... 10 track spotify:track:7w83TSpDeFsL8dV9pAHWn6 https://open.spotify.com/track/7w83TSpDeFsL8dV... ZZ Top's First Album E minor E minor

447622 rows × 36 columns

In [ ]:
dataset.shape
Out[ ]:
(447622, 36)
In [ ]:
dataset.columns
Out[ ]:
Index(['artist_name', 'artist_id', 'album_id', 'album_type',
       'album_release_date', 'album_release_year',
       'album_release_date_precision', 'danceability', 'energy', 'key',
       'loudness', 'mode', 'speechiness', 'acousticness', 'instrumentalness',
       'liveness', 'valence', 'tempo', 'track_id', 'analysis_url',
       'time_signature', 'disc_number', 'duration_ms', 'explicit',
       'track_href', 'is_local', 'track_name', 'track_preview_url',
       'track_number', 'type', 'track_uri', 'external_urls.spotify',
       'album_name', 'key_name', 'mode_name', 'key_mode'],
      dtype='object')
In [ ]:
dataset.loc[191428]
Out[ ]:
artist_name                                                           Linkin Park
artist_id                                                  6XyY86QOPPrYVGvF9ch6wz
album_id                                                   6PFPjumGRpZnBzqnDci6qJ
album_type                                                                  album
album_release_date                                                     2000-10-24
album_release_year                                                         2000.0
album_release_date_precision                                                  day
danceability                                                                0.556
energy                                                                      0.864
key                                                                             3
loudness                                                                    -5.87
mode                                                                            0
speechiness                                                                0.0584
acousticness                                                              0.00958
instrumentalness                                                              0.0
liveness                                                                    0.209
valence                                                                       0.4
tempo                                                                     105.143
track_id                                                   7q115ia4fQn9zonjpexWsY
analysis_url                    https://api.spotify.com/v1/audio-analysis/7q11...
time_signature                                                                  4
disc_number                                                                     1
duration_ms                                                                216880
explicit                                                                    False
track_href                      https://api.spotify.com/v1/tracks/7q115ia4fQn9...
is_local                                                                    False
track_name                                                             In the End
track_preview_url               https://p.scdn.co/mp3-preview/6ce8bcf317e8c562...
track_number                                                                    8
type                                                                        track
track_uri                                    spotify:track:7q115ia4fQn9zonjpexWsY
external_urls.spotify           https://open.spotify.com/track/7q115ia4fQn9zon...
album_name                                                          Hybrid Theory
key_name                                                                       D#
mode_name                                                                   minor
key_mode                                                                 D# minor
Name: 191428, dtype: object

La data contiene 447662 canciones de spotify, incluyendo sus detalles (artista, álbum, track), ids internos de spotify así como variables numéricas, que representan atributos (features) musicales de esta.

Por ejemplo:

  • acousticness ([0-1], estima que tan acústica es la canción)
  • danceability ([0-1], estima que tan bailable es la canción)
  • energy ([0-1], estima la intensidad y actividad de la canción)
  • instrumentalness ([0-1], estima que tan instrumental es la canción)
  • liveness ([0-1] estima si la canción es en vivo o no)
  • loudness ([-60,0] estima la sonoridad de la canción, en decibeles promedio)
  • speechiness ([0-1] estima la presencia de palabras en la canción)
  • valence ([0-1], estima la positividad de la canción)
  • tempo (mide los beats por minuto de la canción)
  • key (integer 0 a 11, el tono de la canción. 0=Do, 1=Do#, 2=Re, etc)
  • mode ({0,1}: 0 = Major, 1=Minor)
  • time_signature (integer 3 a 7, estima el tiempo rítmico, valor 3 significa que es 3/4)
  • duration_ms (duración en milisegundos de la canción)

(Mas info: https://developer.spotify.com/documentation/web-api/reference/#/operations/get-audio-features)

In [ ]:
## Seleccion de algunas columnas (features)
col_features = ['acousticness', 'danceability', 'energy', 'instrumentalness', 'liveness', 
               'loudness','speechiness','valence','tempo', 'key', 
               'mode', 'time_signature', 'duration_ms']

## Mostrar algunas estadisticas
dataset[col_features].describe()
Out[ ]:
acousticness danceability energy instrumentalness liveness loudness speechiness valence tempo key mode time_signature duration_ms
count 447622.000000 447622.000000 447622.000000 447622.000000 447622.000000 447622.000000 447622.000000 447622.000000 447622.000000 447622.000000 447622.000000 447622.000000 4.476220e+05
mean 0.698733 0.391103 0.340512 0.506068 0.221720 -18.671858 0.068922 0.337392 108.736640 5.061246 0.683420 3.727714 2.291097e+05
std 0.369360 0.183373 0.317557 0.413743 0.219318 8.640242 0.094167 0.274319 31.650459 3.491417 0.465142 0.717955 1.766587e+05
min 0.000000 0.000000 0.000000 0.000000 0.000000 -60.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 1.066000e+03
25% 0.394000 0.252000 0.075600 0.001690 0.096800 -24.445000 0.037800 0.089400 82.391000 2.000000 0.000000 4.000000 1.234400e+05
50% 0.923000 0.370000 0.210000 0.715000 0.123000 -19.476500 0.044300 0.274000 105.723500 5.000000 1.000000 4.000000 1.949610e+05
75% 0.986000 0.514000 0.582000 0.901000 0.253000 -11.644000 0.058400 0.537000 131.051000 8.000000 1.000000 4.000000 2.715600e+05
max 0.996000 0.986000 1.000000 1.000000 1.000000 0.496000 0.971000 0.996000 244.952000 11.000000 1.000000 5.000000 4.796395e+06

Paso 1: Selección, limpieza y preprocesamiento de features¶

  • Decida que columnas de los datos usará para su clustering. Se sugiere usar las columnas numéricas (dataset[col_features]), pero puede incluir o sacar otras columns si lo desea que no sean consideradas pertinentes para clusterizar (por ejemplo, el año de la canción, o su duración).

  • Decida qué filas (datos) utilizará. Puede pre-seleccionar un subconjunto de datos para el trabajo. Por ejemplo, canciones de los 90's, o canciones no-acústicas.

  • Preprocese los datos. Por ejemplo, recuerde que ciertos algoritmos necesitan que los datos estén en la misma escala, y esto no necesariamente se cumple. Podrían haber datos no válidos o outliers que quisiera sacar.

Requisito: Describa en la ventana siguiente las decisiones que tomó al respecto, indicando las razones para tomar esta decisión. En las ventanas siguientes, ponga los códigos utilizados (sólo los necesarios) para poder reproducir el resultado final.

Respuesta:

  1. Inicialmente usaremos las columnas recomendadas col_feartures

  2. Usaremos solamente canciones donde sus albums fueron lanzados el año 2000 con el objetivo de minimizar los recursos computacionales requeridos.

  3. Se hizo escalamiento de datos y se limpiaron:

    • Canciones duplicadas: Se observa que existen canciones duplicadas dentro del dataset original. Queremos mantener canciones unicas, ya que de lo contrario obtendremos un sobre muestreo de estas dentro del modelo. Se aplicaron distintas librerias de texto para lograr encontrar canciones con el mismo nombre.
    • Features altamente correlacionadas entre si: La multicolinealidad es un problema que puede afectar los modelos de Análisis de Componentes Principales (PCA, por sus siglas en inglés). Por lo tanto, para este caso dropeamos la variables loudness sobre energy, las cuales tienen una correlacion por sobre 0.85.
    • Outliers bajo metodologia 3 desviaciones estandar: Sabemos que existen outliers los cuales pueden afectar en la variabilidad de nustros PCA. Dicho esto, se toma la decisión de usar 3 desviaciones estandar para detectar outliers dentro de nuestro dataset. Este metodo no es tan agresivo como uno de rango intercuartil, y nos permitira conservar mas datos para nuestro estudio. Se intento aplicar metodologia LOF, pero empeoraba la varianza explicada por 2 PCA.

    De esta manera nuestro subconjunto de datos pasa de tener 4103 registros y 13 features a 2879 resgirtos y 12 features.

Subconjunto de datos¶

In [ ]:
#Subconjunto canciones del año 2000

dataset_sub = dataset[dataset['album_release_year']==2000]
dataset_sub.shape
Out[ ]:
(4103, 36)

Seleccionamos canciones del año 2000

Duplicados¶

In [ ]:
#Limpieza de duplicados

#Primero debemos trabajar los titulos de las canciones para poder identificarlos lo mejor posible.

#Limpiamos espacios extras
dataset_sub.loc[:,['artist_name','track_name']] = dataset_sub[['artist_name','track_name']].apply(lambda x: x.str.strip())

#Limpiamos caracteres especiales y puntuaciones
def strip_words(x):
    x = str(x)
    non_words = list(punctuation)
    sentece = ''.join([letra for letra in x if letra not in non_words]) #Quitamos carateres y puntuaciones raras
    new_sentence = unidecode(sentece)
    return new_sentence

dataset_sub.loc[:,['artist_name','track_name']] = dataset_sub[['artist_name','track_name']].applymap(strip_words)

#Dejamos todo en minusculas
dataset_sub.loc[:,['artist_name','track_name']] = dataset_sub[['artist_name','track_name']].apply(lambda x: x.str.title())

#Chequeamos duplicados
print(f"Nº de canciones duplicadas: {dataset_sub.duplicated(subset=['artist_id','track_name']).sum()}")


#Existen canciones duplicadas. Dicho esto trataremos de dejar solamente las que no son en vivo (para un mejor sonido) y y las mas recientes.
dataset_sub = dataset_sub.sort_values(by=['artist_name','track_name','liveness','album_release_year'], ascending=[True,True,True,False])

#Luego de ordenar la data, nos aseguramos de quedarnos con el primer registro.
dataset_sub = dataset_sub.drop_duplicates(subset=['artist_name','track_name'],keep='first') 
Nº de canciones duplicadas: 959

Despues del tratamiento que hicimos, pudimos detectar 959 canciones duplicadas. Tratamos de quedarnos con las que no eran en vivo y con fecha de lanzamiento mas reciente.

Seleccion de features¶

In [ ]:
#Selección de features recomendadas
data = dataset_sub[col_features]

#Adicionalmente se obtienen las columnas artist_name, album_name, track_name y track_id para poder crear nuestro playlist mas adelante
data_info = dataset_sub[['artist_name','album_name','track_name','track_id']]

print(data.shape,data_info.shape)
(3144, 13) (3144, 4)

Nuestras variables independientes inciales estaran sujetas a las columnas recomendadas col_features. Adicionalmente extramos informacion necesaria para crear nuestras playlist mas adelante y las guardaremos en la variable data_info.

Chequeo de datos faltantes¶

In [ ]:
#Chequeamos si existen datos faltantes
print(data.isna().sum(), data_info.isna().sum())

#No existen datos faltantes
acousticness        0
danceability        0
energy              0
instrumentalness    0
liveness            0
loudness            0
speechiness         0
valence             0
tempo               0
key                 0
mode                0
time_signature      0
duration_ms         0
dtype: int64 artist_name    0
album_name     0
track_name     0
track_id       0
dtype: int64

No tenemos datos faltantes.

Correlacion entre features¶

In [ ]:
#Observamos si existe correlacion entre variables. Si existe alguna con una correlacion >0.75, tendremos que dropear una de las 2.

plt.figure(figsize=(15,10))
sns.heatmap(data=data.corr(), annot=True, fmt=".2f", cmap="YlGnBu")
plt.show()

La variable loudness y energy tienen una alta correlacion, por lo tanto no aportaran mucho al modelo si dejamos ambas. Dicho esto dropearemos loudness.

Adicionalmente se pueden observar otras correlaciones entre variables que las podres observar con mas detalle en el biplot.

In [ ]:
#La variable loudness y energy tienen una alta correlacion. Dropearemos loudness.

data = data.drop('loudness', axis=1)
data.head()
Out[ ]:
acousticness danceability energy instrumentalness liveness speechiness valence tempo key mode time_signature duration_ms
387 0.608 0.506 0.413 0.000000 0.2140 0.2630 0.581 98.720 6 1 4 140373
384 0.177 0.718 0.666 0.000003 0.2150 0.3610 0.643 120.544 11 1 5 182440
385 0.183 0.612 0.704 0.000000 0.1130 0.5600 0.140 82.276 8 0 4 294400
379 0.126 0.603 0.733 0.000008 0.0766 0.4270 0.489 82.902 8 1 4 176866
399 0.433 0.758 0.542 0.000000 0.0857 0.0502 0.591 93.377 1 0 4 199800

Escalamiento de datos¶

In [ ]:
#Dada la diferencia de magnitud de los datos, se debe normalizar los datos, de lo contrario nuestros modelos no nos daran buenos resultados.

scaler = StandardScaler()
data_scaled = pd.DataFrame(scaler.fit_transform(data),columns=data.columns, index=data.index)
data_scaled
Out[ ]:
acousticness danceability energy instrumentalness liveness speechiness valence tempo key mode time_signature duration_ms
387 0.263767 0.265679 -0.223575 -0.711196 -0.055430 2.223782 0.625767 -0.479719 0.183876 0.738730 0.304075 -0.660752
384 -0.823009 1.355166 0.551399 -0.711187 -0.050841 3.349758 0.848299 0.222255 1.660895 0.738730 2.167648 -0.469731
385 -0.807880 0.810422 0.667798 -0.711196 -0.518883 5.636179 -0.957086 -1.008645 0.774683 -1.353674 0.304075 0.038664
379 -0.951607 0.764171 0.756629 -0.711175 -0.685909 4.108069 0.295557 -0.988509 0.774683 0.738730 0.304075 -0.495042
399 -0.177500 1.560729 0.171570 -0.711196 -0.644152 -0.221194 0.661659 -0.651578 -1.293143 -1.353674 0.304075 -0.390902
... ... ... ... ... ... ... ... ... ... ... ... ...
445419 -0.870918 1.545312 0.533020 -0.711000 -0.482173 -0.180980 0.758568 -0.597766 -0.111528 0.738730 0.304075 0.154787
445406 -1.048434 0.507216 0.710682 -0.711192 1.032077 3.372737 0.909316 1.307028 -1.293143 0.738730 0.304075 0.499407
445385 -1.243599 0.954317 1.194658 -0.711186 0.541093 2.591448 1.034939 1.960754 0.479279 0.738730 0.304075 -0.261278
445396 1.000051 0.460964 -0.701424 -0.711196 0.463086 3.981683 0.719087 -0.739196 -1.293143 0.738730 0.304075 -0.831548
445402 -1.175014 1.447669 0.147065 -0.673413 -0.014132 -0.102851 0.378109 -0.824434 0.479279 0.738730 0.304075 -0.180814

3144 rows × 12 columns

Detección de outliers¶

In [ ]:
#Veremos si existen outliers (Al menos usando boxplots).

data_scaled.boxplot()
plt.xticks(rotation=45,ha='right')
plt.title('data sin reduccion de outliers')
plt.show()
In [ ]:
#Usando el metodo de 3 desvaciones estandar. Veremos cuantas filas contienen a lo menos 1 outlier.

outliers = (data_scaled>3 )|(data_scaled<-3)

print(f"Porcentaje de filas con outliers con metodo de 3 desvaciones estandar: {outliers.any(axis=1).mean():.2%}")

#Graficando
model = PCA(n_components=2).fit(data_scaled)
x_pca = model.transform(data_scaled)
sns.scatterplot(x = x_pca[:,0], y = x_pca[:,1], hue = outliers.any(axis=1), palette = ['#33658a','#fe4a49'])
plt.title('outliers con 3 desviaciones estandar')
plt.show()
Porcentaje de filas con outliers con metodo de 3 desvaciones estandar: 8.43%
In [ ]:
# Ahora este ultimo metodo no seria ten eficiente si no tenemos distribuciones normales,lo que justamente aqui no pasa para todas las variables.
plt.rcdefaults()
data_scaled.hist(grid=False)
plt.tight_layout()
plt.show()

Acorde a lo anterior tenemos que 8.43% de las filas tienen al menos 1 outlier, pero que este modelo trabaja bien sobre distribuciones normales, lo cual no es 100% cierto para todas las variables en nuestro dataset. Ademas, sabemos que este metodo no toma en cuenta la interacción entre variables. Dicho esto usaremos SVM para detectar outliers, usando como proxy de contaminacion el 8.43% del modelo anterior.

In [ ]:
# Acorde a lo anterior tenemos que 8.43% de las filas tienen al menos 1 outlier, pero sabemos que este metodo no toma en cuenta la interacción entre variables.
# Dicho esto usaremos SVM para detectar outliers, usando como proxy de contaminacion el 8.43% del modelo anterior.

outliers = OneClassSVM(nu=0.0843, kernel = 'rbf', gamma = 'auto',max_iter=-1).fit_predict(data_scaled)

print(f"Porcentaje de filas con outliers con medelo SVM - kernel rbf: {(outliers==-1).sum()/data_scaled.shape[0]:.2%}")

#Graficando
model = PCA(n_components=2).fit(data_scaled)
x_pca = model.transform(data_scaled)
sns.scatterplot(x = x_pca[:,0], y = x_pca[:,1], hue = [True if i == -1 else False for i in outliers], palette = ['#33658a','#fe4a49'])
plt.title('outliers con SVM - kernel rbf')
plt.show()
Porcentaje de filas con outliers con medelo SVM - kernel rbf: 8.49%

Finalmente nos filtramos los outliers bajo el modelo SVM

In [ ]:
#Con la intencion de mantener consitencia entre ambos datasets, se eliminan los outliers de ambos. Recordemos que vamos a necesitar la info para crear nuestra playlist.

no_outliers = [False if i == -1 else True for i in outliers]

# Nos quedamos con nuestras filas sin outliers para ambos datasets

data_scaled = data_scaled[no_outliers]
data_info = data_info[no_outliers]
In [ ]:
#Visualizamos con reduccion de outliers

data_scaled.boxplot()
plt.xticks(rotation=45,ha='right')
plt.title('data con reduccion de outliers')
plt.show()

Con la intención de mantener la mayoria de nuestros datos usamos el modelo SVM. De este modo solo eliminamos el ~8.4% de las filas totales.

Dataset final¶

In [ ]:
data_scaled.shape
Out[ ]:
(2877, 12)

Nos quedamos con un data set de casi 2900 filas y 12 variables independientes.

Paso 2: Clustering de canciones¶

  • Realice alguna de las técnica de clustering vistas en el curso a las canciones seleccionadas. Pruebe distintas alternativas. si es pertinente realice calibración de los hiperparámetros (# clusters, métrica a usar, tipo de linkage si usa cluster jerárquicos, etc)

Requisito: Describa en la ventana siguiente las técnicas que probó, así como el modelo seleccionado (con sus valores de hiperparámetros finales). En las siguientes ventanas, ponga los códigos utilizados para poder reproducir el resultado final. (Ojo: no necesita poner todos los códigos de todos los métodos que probó. Basta con aquellos relacionados con el modelo final seleccionado).

Respuesta:

Se usaron distintos metodos a modo de obtener un modelo que nos diera un mejor resultado, buscando el punto de quiebre mas pronunciado usando la metodologia del codo, asi como el score mas alto para silueta y el mas bajo para davis boulding.

De esta manera, se presenta a continuacion una tabla con los resultados para cada metodo y su puntuacion respectiva

Mejor Nº de cluster segun score:

Modelo Metodo Codo Silueta Boulding
K-Means 2 2 2
C-Means 2 2 2
GMM 2 2 2

Ahora bien, se decide continuar con K-Means para este estudio, ya que presenta unscore de silueta y boulding levemente mejor que el resto. Recordemos que un score silueta mas alto y un boulding mas bajo es mejor.

Modelo n_clusters silueta davies_bouldin
K-Means 2 0.250665 1.551570
C-Means 2 0.247316 1.580740
GMM-diag 2 0.238371 1.587731

El modelo final consiste en los siguientes hiperparametros:

KMeans( n_clusters=2,n_init='auto', init='k-means++', random_state=0, max_iter=300, tol=0.0001)

K-Means¶

In [ ]:
#Buscando el mejor modelo

#Se recomienda usar max cluser = numero de atributos + 1
max_n_clusters = data_scaled.shape[1]+1

## Almacenado de errores al cuadrado
sse = []

for k in range(1, max_n_clusters):

    ## Instancia del modelo
    kmeans_model = KMeans(n_clusters=k,n_init='auto', init='k-means++',random_state=0, max_iter=300, tol=0.0001)

    ## Ajuste del modelo
    kmeans_model.fit(data_scaled)

    ## Guardar el SSE obtenido
    sse.append(kmeans_model.inertia_)

plt.figure(figsize=(10, 5))
plt.plot(range(1, max_n_clusters), sse)
plt.title('Elbow curve')
plt.xlabel('Numero de cluster')
plt.ylabel('SSE')
plt.tight_layout()
plt.show()
In [ ]:
max_n_clusters = data_scaled.shape[1]+1

## Almacenado de SSE-score
sse = []
silueta_km_lst = []
davies_bouldin_km_lst = []
n_cluster_lst = []

for k in range(2, max_n_clusters):

    ## Instancia del modelo
    kmeans_model = KMeans(n_clusters=k,n_init='auto', init='k-means++',random_state=0, max_iter=300, tol=0.0001)

    ## Ajuste del modelo
    kmeans_model.fit(data_scaled)


    # Cálculo de métricas para K-Means
    silueta_km = silhouette_score(data_scaled, kmeans_model.labels_)
    davies_bouldin_km = davies_bouldin_score(data_scaled, kmeans_model.labels_)
    silueta_km_lst.append(silueta_km)
    davies_bouldin_km_lst.append(davies_bouldin_km)
    n_cluster_lst.append(k)


silueta_davis = pd.DataFrame()

silueta_davis['n_clusters'] = n_cluster_lst
silueta_davis['silueta']=silueta_km_lst 
silueta_davis['davies_bouldin']=davies_bouldin_km_lst
print(silueta_davis)

fig, ax = plt.subplots(figsize=(10, 5))
y2 = ax.twinx()

sns.lineplot(data=silueta_davis, x='n_clusters', y='silueta', ax=ax,label='Silueta', color='blue')
sns.lineplot(data=silueta_davis, x='n_clusters', y='davies_bouldin', ax=y2, label='Davies Bouldin', color='red')

# Add labels at the end of each line
silueta_max = last_row_silueta = silueta_davis.iloc[-1]['silueta']
davies_max = last_row_silueta = silueta_davis.iloc[-1]['davies_bouldin']

ax.text(silueta_davis['n_clusters'].iloc[-1], silueta_max, 'Silueta', color='blue', va='center')
y2.text(silueta_davis['n_clusters'].iloc[-1], davies_max, 'Davies Bouldin', color='red', va='center')

# Set labels for both x-axes
ax.set_xlabel('Number of Clusters')
y2.set_xlabel('Number of Clusters')
ax.legend_.remove()
y2.legend_.remove()
plt.xlim(1, 14)
plt.title('Score de silueta y davies_bouldin')
plt.show()
    n_clusters   silueta  davies_bouldin
0            2  0.250665        1.551570
1            3  0.183340        2.027633
2            4  0.152244        2.120622
3            5  0.161261        1.949095
4            6  0.162803        1.894481
5            7  0.145194        1.966472
6            8  0.155780        1.949053
7            9  0.134181        1.877869
8           10  0.135119        1.801033
9           11  0.138361        1.769663
10          12  0.133374        1.740106

Guiandonos por la metodologia del codo y complementando con las metricas de silueta y davis bouldin, obtenemos que el numero cluster optimos en 2.

  • silhouette_score -> entre más alto mejor
  • davies_bouldin_score -> entre más bajo mejor
In [ ]:
#Modelo con mejor parametro

kmeans_model = KMeans(n_clusters=2,n_init='auto', init='k-means++',random_state=0, max_iter=300, tol=0.0001)

## Ajuste del modelo con los datos
kmeans_model.fit(data_scaled)

## Transformación de los datos usando PCA
pca_model = PCA(n_components=2, random_state=0)
pca_model.fit(data_scaled)
pca_data = pca_model.transform(data_scaled)

## Crear un dataframe con los datos transformados mediante PCA y agregar las etiquetas de los clusteres
pca_data = pd.DataFrame(pca_data, columns=['PC1', 'PC2'])
pca_data['cluster'] = kmeans_model.predict(data_scaled)

## Graficar los datos por clases. 
N = pca_data['cluster'].nunique()

plt.figure(figsize=(8, 6))
sns.scatterplot(data=pca_data, 
                x='PC1', y='PC2', 
                hue='cluster',
                palette=sns.color_palette()[:N])
plt.legend(loc=[1.01, 0.5], title='Cluster')
plt.title('Cluster formados')
plt.show()

Paso 3: Interpretación de los clusters obtenidos¶

  • Intente comprender los clusters obtenidos. Utilice las técnicas vistas en el curso (análisis de medias, PCA, etc) para analizar los clusters, intentando interpretar el por qué de la clusterización que obtuvo.

Requisito: Describa en la ventana siguiente los análisis que hizo, así como la conclusión a la que llegó. Incluya en la ventana siguiente los códigos finales utilizados para llegar a esta conclusión.

Respuesta:

A modo de describir nuestros 2 cluster formados anteriormente, usamos analisis 2D. Luego finalizamos usando boxplot y analisis de medias para describir mas en detalle cada grupo.

Clúster 0: Las canciones en este clúster tienen una "acousticness" relativamente baja, lo que significa que es poco probable que tengan elementos acústicos, pero si mas electrónicos. La "danceability" es razonablemente alta, lo que sugiere que son adecuadas para bailar. Tienen una alta "energy", lo que indica un alto nivel de energía y ritmo. La "instrumentalness" es baja, lo que sugiere que estas canciones probablemente contienen voces. La "liveness" es moderada-baja, lo que indica que la mayoria de las canciones son grabaciones de estudio en lugar de actuaciones en vivo. La "speechiness" es baja, lo que apunta que probablemente es muscia y no un talk show, audio book, etc. La "valence" es relativamente alta, lo que sugiere un tono alegre. El "tempo" es rápido, con un valor de 121.73 BPM. Las canciones en este clúster están en modo mayor. La "duration_ms" es moderada en términos de duración.

Clúster 1: En contraste, las canciones en este clúster tienen una alta "acousticness", lo que sugiere que son predominantemente acústicas. La "danceability" es mas baja, lo que indica que no son muy adecuadas para el baile. Tienen una baja "energy", lo que sugiere un nivel de energía y ritmo más bajo. La "instrumentalness" es alta, lo que sugiere que estas canciones tienen menos probabilidad de contener voces. La "liveness" es baja, lo que sugiere que son grabaciones de estudio. La "speechiness" es baja, lo que indica que probablemente es muscia y no un talk show, audio book, etc. La "valence" es mas baja, lo que sugiere un tono un poco mas melancolico. El "tempo" es más lento, con un valor de 103.32 BPM. Estas canciones están en modo mayor aunque levemente mas bajo que el cluster 0. La "duration_ms" es más larga en comparación con el clúster 0. Finalmente su time_signature tiene una variación importante si se le compara con el otro cluster.

En resumen, el clúster 0 parece estar compuesto por canciones más enérgicas y adecuadas para bailar, mientras que el clúster 1 está formado por canciones predominantemente acústicas con un ritmo más lento y una energía más relajada, pero con un tono mas melancolico. Ambos clústeres tienen características similares en términos de key, mode y speechiness, pero difieren en las demás métricas.

In [ ]:
#Varianza explicada por cantidad de PCA

pca_model = PCA()
pca_model.fit_transform(data_scaled)
pca_var = pd.DataFrame([pca_model.explained_variance_ratio_,
              pca_model.explained_variance_ratio_.cumsum()]).T \
            .rename(columns={0:'variance',1:'cumulative_variance'})
pca_var['N_PCA']  = [i+1 for i in range (len(pca_var.variance))]
pca_var
Out[ ]:
variance cumulative_variance N_PCA
0 0.349398 0.349398 1
1 0.105933 0.455331 2
2 0.096997 0.552328 3
3 0.090348 0.642676 4
4 0.081498 0.724174 5
5 0.069014 0.793188 6
6 0.056463 0.849651 7
7 0.044237 0.893888 8
8 0.037231 0.931119 9
9 0.033441 0.964560 10
10 0.024764 0.989324 11
11 0.010676 1.000000 12

2 PCA explican 45.9% de los datos.

In [ ]:
#Para poder visualizar los datos en un espacio 2D, usaremos las primeras 2 componentes.
model = pca(verbose=False,n_components=2,random_state=0)

#En la siguiente tabla se observa que features contribuyen mas a la varianza de las primeras 2 componentes.
model.fit_transform(data_scaled)['topfeat']
Out[ ]:
PC feature loading type
0 PC1 energy -0.469967 best
1 PC2 mode -0.766313 best
2 PC1 acousticness 0.447399 weak
3 PC1 danceability -0.393230 weak
4 PC1 instrumentalness 0.364452 weak
5 PC1 liveness -0.162428 weak
6 PC1 speechiness -0.106605 weak
7 PC1 valence -0.420971 weak
8 PC1 tempo -0.195038 weak
9 PC2 key 0.621789 weak
10 PC1 time_signature -0.103514 weak
11 PC1 duration_ms 0.158561 weak

Las variables que influyen mas en en PC1, PC2 son energy y mode respectivamente.

In [ ]:
#Ahora iremos harmando nuestro biplot, para ver que features y que magnitud corresponde a cada cluster.

#Primero nuestro scatterplot con la etiquetas respectivas para nuestras PC1 y PC2
model.scatter(labels=kmeans_model.labels_, legend=True,figsize=(15,8))
plt.show()
[scatterd] >INFO> Create scatterplot
In [ ]:
# 2D plot with samples colored on color_intensity
model.biplot(labels=kmeans_model.labels_, legend=True, cmap=None,figsize=(15,8))
plt.show()
[scatterd] >INFO> Create scatterplot

Aqui podemos observar nuevamente la correlacion entre variables en un espacio de 2 dimensiones. Por ejemplo, energy, danceability y valence estan correlacionadas entre si ya que se mueven en la misma direccion y son muy cercanas en el plano. Lo mismo podemos decir de instrumentalness y acousticness.

In [ ]:
# 2D plot with samples colored on color_intensity
model.biplot(labels=kmeans_model.labels_, legend=True,alpha=0.4,figsize=(15,8),cmap='Pastel2')
plt.show()
[scatterd] >INFO> Create scatterplot

Del grafico podemos analizar en base a la orientacion de la flecha y la correlacion asociada para cada feature, que caracteristicas tiene cada cluster.

Se puede desprender que el cluster 0 tiene canciones con "energy" mas alta que el cluster 1, asi como tambien danceability y valence. Del otro lado, tenemos que el cluster 1 tiene mas instrumentales y acousticness en comparacion al cluster 0.

In [ ]:
data_scaled['cluster'] = kmeans_model.labels_
data_melt = data_scaled.melt(id_vars=['cluster'])

plt.rcdefaults()

# Create a figure with subplots
fig, axes = plt.subplots(1,len(data_melt['cluster'].unique()), figsize=(12, 5))

# Enumerate through unique cluster values
for i, col in enumerate(data_melt['cluster'].unique()):
    # Select the axis for the current subplot
    ax = axes[i]
    
    # Filter the data for the current cluster
    subset_data = data_melt[data_melt['cluster'] == col]
    
    # Create a point plot on the current axis
    sns.boxplot(data=subset_data, x='variable', y='value', ax=ax,showfliers=False)
    
    # Customize the x-axis labels (rotation, alignment)
    ax.set_xticklabels(ax.get_xticklabels(), rotation=45, ha='right',fontsize=10)
    # Add a title to the subplot
    ax.set_title(f'Cluster {col}')
    ax.set_xlabel('')
    
# Adjust spacing between subplots
plt.tight_layout()

# Show all subplots together
plt.show()
In [ ]:
#Volvemos nuestros datos al estado original para evaluar en base a las documentacion de spotify
df_detail = pd.DataFrame(scaler.inverse_transform(data_scaled.drop(columns='cluster')),columns=data_scaled.drop(columns='cluster').columns,index=data_scaled.index)
df_detail['cluster'] = kmeans_model.labels_
In [ ]:
df_detail.groupby('cluster').mean().reset_index()
Out[ ]:
cluster acousticness danceability energy instrumentalness liveness speechiness valence tempo key mode time_signature duration_ms
0 0 0.245993 0.559010 0.706121 0.082332 0.264044 0.072672 0.558234 120.836740 5.563073 0.662844 3.951261 239157.172592
1 1 0.885446 0.303837 0.162496 0.537127 0.150072 0.043350 0.180506 102.763305 5.049426 0.651368 3.762577 332163.961165

Clúster 0: Las canciones en este clúster tienen una "acousticness" relativamente baja, lo que significa que es poco probable que tengan elementos acústicos, pero si mas electrónicos. La "danceability" es razonablemente alta, lo que sugiere que son adecuadas para bailar. Tienen una alta "energy", lo que indica un alto nivel de energía y ritmo. La "instrumentalness" es baja, lo que sugiere que estas canciones probablemente contienen voces. La "liveness" es moderada-baja, lo que indica que la mayoria de las canciones son grabaciones de estudio en lugar de actuaciones en vivo. La "speechiness" es baja, lo que apunta que probablemente es muscia y no un talk show, audio book, etc. La "valence" es relativamente alta, lo que sugiere un tono alegre. El "tempo" es rápido, con un valor de 121.73 BPM. Las canciones en este clúster están en modo mayor. La "duration_ms" es moderada en términos de duración.

Clúster 1: En contraste, las canciones en este clúster tienen una alta "acousticness", lo que sugiere que son predominantemente acústicas. La "danceability" es mas baja, lo que indica que no son muy adecuadas para el baile. Tienen una baja "energy", lo que sugiere un nivel de energía y ritmo más bajo. La "instrumentalness" es alta, lo que sugiere que estas canciones tienen menos probabilidad de contener voces. La "liveness" es baja, lo que sugiere que son grabaciones de estudio. La "speechiness" es baja, lo que indica que probablemente es muscia y no un talk show, audio book, etc. La "valence" es mas baja, lo que sugiere un tono un poco mas melancolico. El "tempo" es más lento, con un valor de 103.32 BPM. Estas canciones están en modo mayor aunque levemente mas bajo que el cluster 0. La "duration_ms" es más larga en comparación con el clúster 0. Finalmente su time_signature tiene una variación importante si se le compara con el otro cluster.

En resumen, el clúster 0 parece estar compuesto por canciones más enérgicas y adecuadas para bailar, mientras que el clúster 1 está formado por canciones predominantemente acústicas con un ritmo más lento y una energía más relajada, pero con un tono mas melancolico. Ambos clústeres tienen características similares en términos de key, mode y speechiness, pero difieren en las demás métricas.

Paso 4: Selección de playlist¶

  • Escoga una canción de la lista, a partir de la cual le gustaría hacer una playlist.

  • Vea el cluster asociado a esa canción, y seleccione otras canciones del mismo clusters, para crear una playlist de ~3 horas de duración. Para esto último, utilice el campo duration_ms que indica la duración de la canción.

  • Escriba la lista de canciones seleccionadas, indicando su título y artista/grupo.

Requisito: Indique la canción seleccionada, así como la lista generada. Agregue después lo códigos utilizados para esto.

Respuesta:

La cancion seleccionada corresponde a Papercut de Linkin Park. Esta cancion pertenece al cluster 0.

La playlist dura aproximadamente 3 horas y se encuentra disponible en el siguiente link:

https://open.spotify.com/playlist/74NwjpUzI8VXxi9jsnWVWH

Canciones:

artist_name track_name
Los Tres Ases Ofrenda
Los Autenticos Decadentes El Dinero No Es Todo
Los Jaivas Amores De Antes
Sr71 Right Now
Andres Calamaro La Verdadera Libertad
Ben Harper Like A King
The Offspring All Along
Quilapayun El Sabioloco
Limp Bizkit Full Nelson
Alejandro Sanz Dale Al Aire Con Juan Habichuela Y Ketama
Helloween Escalation 666
Deep Purple Lucille Live 1972
Therion Flesh Of The Gods
Pyotr Ilyich Tchaikovsky The Nutcracker Op 71 C Danse Russe Trepak Temp...
Ricky Martin She Bangs English Version
Los Miserables Quien Mato A Marilyn
Caetano Veloso Zumbi
Diego Torres La Ultima Noche
Caetano Veloso Meu Rio
Franco De Vita Te Amo En Vivo
Miguel Bose Dulce Pesadilla
Helloween I Live For Your Pain
Divididos El Arriero Live
Los Terricolas Te Juro Que Te Amo
Deftones Teenager
Nightwish Dead Boys Poem
Quilapayun Tio Caiman
La Oreja De Van Gogh Pop
Linkin Park Cure For The Itch
Limp Bizkit My Way
Acdc Give It Up
U2 When I Look At The World
Tiesto Airtight
Millencolin Hellman
Sergio Mendes Going Out Of My Head
Los Terricolas Dos Cosas
Outkast Stankonia Stanklove Feat Big Rube Sleepy Brown
Gondwana Libros Sagrados
Limp Bizkit Boiler
Alejandro Sanz El Alma Al Aire Extended Remix
The Offspring Special Delivery
Luis Fonsi Mi Sueno
Tommy Guerrero Soul Miner
Andres Calamaro Papa Say
In [ ]:
df = pd.concat([data_info,df_detail],axis=1).reset_index(drop=True)
df.head(5)
Out[ ]:
artist_name album_name track_name track_id acousticness danceability energy instrumentalness liveness speechiness valence tempo key mode time_signature duration_ms cluster
0 2Pac The Rose That Grew From Concrete A River That Flows Forever 0FvMwjhIU2xsQUj6jKZWrU 0.608 0.506 0.413 0.000000 0.2140 0.2630 0.581 98.720 6.0 1.0 4.0 140373.0 0
1 2Pac The Rose That Grew From Concrete Can U C The Pride In The Panther Album Versio... 1tUtkxKxeDlyV8y5BPusyI 0.126 0.603 0.733 0.000008 0.0766 0.4270 0.489 82.902 8.0 1.0 4.0 176866.0 0
2 2Pac The Rose That Grew From Concrete Family Tree 1kxnbvzdnFW2uleKZx6gIC 0.433 0.758 0.542 0.000000 0.0857 0.0502 0.591 93.377 1.0 0.0 4.0 199800.0 0
3 2Pac The Rose That Grew From Concrete God 1NzCGSToYZti9uKubFHBxx 0.938 0.616 0.123 0.080600 0.1160 0.2250 0.433 76.863 6.0 0.0 3.0 47866.0 1
4 2Pac The Rose That Grew From Concrete If There Be Pain 0GmCjP5g3yMqqBWhDGjw87 0.181 0.614 0.559 0.000000 0.1630 0.0413 0.631 81.456 9.0 1.0 4.0 271693.0 0
In [ ]:
# Uno de mis grupos favoritos es Linkin Park. Por lo que seleccionare el cluster de esta banda para compararlos con otras que sean del mismo.

df[df.artist_name=='Linkin Park'].head()
Out[ ]:
artist_name album_name track_name track_id acousticness danceability energy instrumentalness liveness speechiness valence tempo key mode time_signature duration_ms cluster
1432 Linkin Park Hybrid Theory (Int'l Only DMD w/ Altered iLiner) A Place For My Head 4vsIwS56G2Khi36iLoIZgH 0.0144 0.603 0.908 0.000000 0.671 0.1840 0.457 133.063 11.0 1.0 4.0 184640.0 0
1433 Linkin Park Hybrid Theory (Int'l Only DMD w/ Altered iLiner) By Myself 6CqMnBhJkUNzZqkDq2Dz3u 0.0171 0.544 0.935 0.003010 0.297 0.1730 0.339 103.026 6.0 0.0 4.0 189800.0 0
1434 Linkin Park Hybrid Theory (Int'l Only DMD w/ Altered iLiner) Crawling 0EorNL6AhJkrR2h4IltoaQ 0.0466 0.580 0.702 0.000003 0.536 0.0337 0.299 105.076 4.0 1.0 4.0 208960.0 0
1435 Linkin Park Hybrid Theory (Int'l Only DMD w/ Altered iLiner) Cure For The Itch 27P3adXGQqiHjq0Y0C2fS7 0.1630 0.750 0.677 0.050900 0.247 0.2210 0.851 99.990 8.0 1.0 4.0 157200.0 0
1436 Linkin Park Hybrid Theory (Int'l Only DMD w/ Altered iLiner) Forgotten 6Y7F8eKKHWC7no0Jn0pX1I 0.0132 0.615 0.947 0.000000 0.366 0.1100 0.498 108.193 8.0 0.0 4.0 194426.0 0
In [ ]:
#Seleccionamos la cancion Papercut y extraemos el cluster

cluster = df[(df.artist_name=='Linkin Park')&(df.track_name=='Papercut')].iloc[0,-1]
cluster
Out[ ]:
0
In [ ]:
#Artistas dentro del mismo cluster

df_cluster = df[df.cluster==cluster]
df_cluster.artist_name.unique()
Out[ ]:
array(['2Pac', 'Acdc', 'Alejandro Sanz', 'Alex Bueno', 'Andres Calamaro',
       'Aterciopelados', 'Attaque 77', 'Babasonicos', 'Backstreet Boys',
       'Belle  Sebastian', 'Ben Harper', 'Bersuit Vergarabat', 'Bjork',
       'Black Eyed Peas', 'Bon Jovi', 'Caetano Veloso',
       'Camille Saintsaens', 'Chancho En Piedra', 'Chayanne',
       'Children Of Bodom', 'Coldplay', 'Collective Soul',
       'Daniela Mercury', 'David Arkenstone', 'Deep Purple', 'Deftones',
       'Diego Torres', 'Divididos', 'Elton John', 'Eminem', 'Erasure',
       'Eric Clapton', 'Fatboy Slim', 'Fito Paez', 'Fleetwood Mac',
       'Franco De Vita', 'Gilberto Gil', 'Gondwana', 'Green Day',
       'Hans Zimmer', 'Helloween', 'Iggy Pop', 'In Flames',
       'Intiillimani', 'Ismael Serrano', 'Joan Manuel Serrat',
       'John Coltrane', 'Jose Luis Perales', 'Juanes', 'Julieta Venegas',
       'Kamelot', 'Kevin Johansen', 'La Mosca Tsetse',
       'La Oreja De Van Gogh', 'La Renga', 'Limp Bizkit', 'Linkin Park',
       'Los Angeles Azules', 'Los Autenticos Decadentes',
       'Los Enanitos Verdes', 'Los Jaivas', 'Los Miserables',
       'Los Pericos', 'Los Piojos', 'Los Terricolas', 'Los Tres Ases',
       'Lucybell', 'Luis Fonsi', 'Luis Miguel', 'Lynyrd Skynyrd',
       'Marco Antonio Solis', 'Matchbox Twenty', 'Matt Uelmen',
       'Miguel Bose', 'Millencolin', 'Moby', 'Nacao Zumbi',
       'Nana Mouskouri', 'Neil Young', 'Nightwish', 'Oasis', 'Outkast',
       'Pantera', 'Papa Roach', 'Paul Mccartney', 'Pearl Jam',
       'Pyotr Ilyich Tchaikovsky', 'Queens Of The Stone Age',
       'Quilapayun', 'Radiohead', 'Rage Against The Machine',
       'Reo Speedwagon', 'Rhapsody', 'Ricardo Arjona', 'Ricky Martin',
       'Sergei Rachmaninoff', 'Sergio Mendes', 'Shakira', 'Sr71',
       'Sui Generis', 'Sum 41', 'The Avalanches', 'The Black Keys',
       'The Cure', 'The Hives', 'The Offspring', 'The White Stripes',
       'Therion', 'Thievery Corporation', 'Tiesto', 'Tommy Guerrero',
       'Tracy Chapman', 'U2', 'Vicente Fernandez', 'Victor Manuelle',
       'Wisin  Yandel', 'Wyclef Jean'], dtype=object)
In [ ]:
#Creando playlist de 3 horas
playlist = pd.DataFrame(columns=df_cluster.columns)

while True:

    if playlist.duration_ms.sum() >= 1.08e+7: #3 horas en milisegundos
        break
    else:
        song = df_cluster.sample(1)
        if song.track_id.item() not in playlist.track_id.values:
            playlist = pd.concat([playlist, song])
        else:
            continue

#Nº Canciones en la playlist
print(f'Canciones totales en la playlist: {playlist.shape[0]}')

#Duracion en horas
print(f'Duración en horas: {playlist.duration_ms.sum()/1000/60/60:.2f}') 
Canciones totales en la playlist: 44
Duración en horas: 3.03
In [ ]:
playlist[['artist_name','track_name']]
Out[ ]:
artist_name track_name
1624 Los Tres Ases Ofrenda
1461 Los Autenticos Decadentes El Dinero No Es Todo
1498 Los Jaivas Amores De Antes
2610 Sr71 Right Now
108 Andres Calamaro La Verdadera Libertad
463 Ben Harper Like A King
2691 The Offspring All Along
2259 Quilapayun El Sabioloco
1420 Limp Bizkit Full Nelson
36 Alejandro Sanz Dale Al Aire Con Juan Habichuela Y Ketama
1065 Helloween Escalation 666
749 Deep Purple Lucille Live 1972
2718 Therion Flesh Of The Gods
2188 Pyotr Ilyich Tchaikovsky The Nutcracker Op 71 C Danse Russe Trepak Temp...
2371 Ricky Martin She Bangs English Version
1528 Los Miserables Quien Mato A Marilyn
563 Caetano Veloso Zumbi
770 Diego Torres La Ultima Noche
548 Caetano Veloso Meu Rio
938 Franco De Vita Te Amo En Vivo
1736 Miguel Bose Dulce Pesadilla
1068 Helloween I Live For Your Pain
793 Divididos El Arriero Live
1594 Los Terricolas Te Juro Que Te Amo
765 Deftones Teenager
1860 Nightwish Dead Boys Poem
2290 Quilapayun Tio Caiman
1404 La Oreja De Van Gogh Pop
1435 Linkin Park Cure For The Itch
1427 Limp Bizkit My Way
25 Acdc Give It Up
2799 U2 When I Look At The World
2741 Tiesto Airtight
1766 Millencolin Hellman
2578 Sergio Mendes Going Out Of My Head
1577 Los Terricolas Dos Cosas
1909 Outkast Stankonia Stanklove Feat Big Rube Sleepy Brown
977 Gondwana Libros Sagrados
1419 Limp Bizkit Boiler
38 Alejandro Sanz El Alma Al Aire Extended Remix
2700 The Offspring Special Delivery
1652 Luis Fonsi Mi Sueno
2777 Tommy Guerrero Soul Miner
137 Andres Calamaro Papa Say

BONUS: Conectarse a la API de Spotify¶

Spotify a través de su paquete spotipy permite conectarse a la API de Spotify para hacer busquedas, información, o guardar la lista que creó. Como bonus de su nota (+0.5 puntos) transforme su lista encontrada en una playlist de Spotify, y compartala para el resto del curso.

Para mas información de la API, ver

  • https://developer.spotify.com/documentation/web-api/quick-start/
  • https://medium.com/@maxtingle/getting-started-with-spotifys-api-spotipy-197c3dc6353b
  • https://spotipy.readthedocs.io/
In [ ]:
import spotipy
from spotipy.oauth2 import SpotifyClientCredentials

Obtener información de canciones¶

In [ ]:
#Conectandose con spotify

from spotify_keys import cid, secret

cid = cid
secret = secret

client_credentials_manager = SpotifyClientCredentials(client_id=cid, client_secret=secret)
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
In [ ]:
#Top 20 canciones de polyphia
results = sp.search(q='polyphia', limit=20)
for idx, track in enumerate(results['tracks']['items']):
    print(idx, track['name'])
0 Playing God
1 G.O.A.T.
2 Ego Death (feat. Steve Vai)
3 ABC (feat. Sophia Black)
4 40oz
5 Champagne
6 Ego Death (feat. Steve Vai)
7 O.D.
8 The Worst
9 Neurotica
10 Playing God
11 Reverie
12 Chimera (feat. Lil West)
13 Euphoria
14 Goose
15 Bloodbath (feat. Chino Moreno)
16 ABC (feat. Sophia Black)
17 So Strange (feat. Cuco)
18 All Falls Apart
19 Genesis (feat. Brasstracks)
In [ ]:
#Obtenemos sus features
features_lst=[]

for idx, track in enumerate(results['tracks']['items']):
    trackID = track['uri']
    features = sp.audio_features(trackID)[0]
    features['artist_name'] = track['artists'][0]['name']
    features['track_name'] = track['name']
    features_lst.append(features)

spotify_df = pd.DataFrame(features_lst)
In [ ]:
#Recordemos que dropeamos la variables loudness
col_features = ['acousticness',
 'danceability',
 'energy',
 'instrumentalness',
 'liveness',
 'speechiness',
 'valence',
 'tempo',
 'key',
 'mode',
 'time_signature',
 'duration_ms']

#Escalamos los datos antes de entregarlos al modelo
spotify_scaled = pd.DataFrame(scaler.fit_transform(spotify_df[col_features]),columns=col_features)

#Prediciendo el cluster para cada cancion
predicted_values = kmeans_model.predict(spotify_scaled)

#juntamos todo
pd.concat([spotify_df, pd.Series(predicted_values,name='cluster')], axis=1)
Out[ ]:
danceability energy key loudness mode speechiness acousticness instrumentalness liveness valence ... type id uri track_href analysis_url duration_ms time_signature artist_name track_name cluster
0 0.601 0.789 4 -6.822 0 0.0328 0.039500 0.81200 0.1140 0.315 ... audio_features 3nBGFgfRQ8ujSmu5cGlZIU spotify:track:3nBGFgfRQ8ujSmu5cGlZIU https://api.spotify.com/v1/tracks/3nBGFgfRQ8uj... https://api.spotify.com/v1/audio-analysis/3nBG... 205926 4 Polyphia Playing God 1
1 0.660 0.760 11 -5.488 0 0.0332 0.000601 0.31200 0.0949 0.359 ... audio_features 0YPuRrM2NwzdtuShUKkts6 spotify:track:0YPuRrM2NwzdtuShUKkts6 https://api.spotify.com/v1/tracks/0YPuRrM2Nwzd... https://api.spotify.com/v1/audio-analysis/0YPu... 215999 4 Polyphia G.O.A.T. 0
2 0.655 0.763 2 -5.202 0 0.0526 0.037400 0.67800 0.0703 0.445 ... audio_features 0Qj7PB41b6XgkApKPwDy1r spotify:track:0Qj7PB41b6XgkApKPwDy1r https://api.spotify.com/v1/tracks/0Qj7PB41b6Xg... https://api.spotify.com/v1/audio-analysis/0Qj7... 350086 4 Polyphia Ego Death (feat. Steve Vai) 1
3 0.681 0.827 6 -4.051 0 0.0651 0.055300 0.00000 0.2990 0.750 ... audio_features 7D4lgMbSs10AQ0zX3ZldJc spotify:track:7D4lgMbSs10AQ0zX3ZldJc https://api.spotify.com/v1/tracks/7D4lgMbSs10A... https://api.spotify.com/v1/audio-analysis/7D4l... 152361 4 Polyphia ABC (feat. Sophia Black) 0
4 0.597 0.845 1 -7.803 0 0.0335 0.002670 0.17300 0.2030 0.630 ... audio_features 3v3VFa7Dt32gNR27jfw7DG spotify:track:3v3VFa7Dt32gNR27jfw7DG https://api.spotify.com/v1/tracks/3v3VFa7Dt32g... https://api.spotify.com/v1/audio-analysis/3v3V... 233819 4 Polyphia 40oz 0
5 0.461 0.962 8 -4.665 0 0.1200 0.000013 0.91500 0.3590 0.608 ... audio_features 6ZDHFz6h8d93EPAIq5hDuE spotify:track:6ZDHFz6h8d93EPAIq5hDuE https://api.spotify.com/v1/tracks/6ZDHFz6h8d93... https://api.spotify.com/v1/audio-analysis/6ZDH... 265385 4 Polyphia Champagne 0
6 0.655 0.763 2 -5.202 0 0.0526 0.037400 0.67800 0.0703 0.445 ... audio_features 7xyvWSVkxaoLdSuEdgdhBe spotify:track:7xyvWSVkxaoLdSuEdgdhBe https://api.spotify.com/v1/tracks/7xyvWSVkxaoL... https://api.spotify.com/v1/audio-analysis/7xyv... 350086 4 Polyphia Ego Death (feat. Steve Vai) 1
7 0.633 0.851 11 -5.724 0 0.0685 0.000569 0.89800 0.1400 0.592 ... audio_features 1U2yqd3Bp4vBuIQ3NGfS6C spotify:track:1U2yqd3Bp4vBuIQ3NGfS6C https://api.spotify.com/v1/tracks/1U2yqd3Bp4vB... https://api.spotify.com/v1/audio-analysis/1U2y... 202163 4 Polyphia O.D. 0
8 0.656 0.696 4 -5.935 0 0.0438 0.118000 0.45000 0.1610 0.400 ... audio_features 5l3WNlq4rag0fFP0TI9qeJ spotify:track:5l3WNlq4rag0fFP0TI9qeJ https://api.spotify.com/v1/tracks/5l3WNlq4rag0... https://api.spotify.com/v1/audio-analysis/5l3W... 245779 4 Polyphia The Worst 1
9 0.689 0.679 11 -5.239 0 0.0543 0.022300 0.71000 0.1030 0.922 ... audio_features 0uzwUl56ZPCJtRlqhG5OFo spotify:track:0uzwUl56ZPCJtRlqhG5OFo https://api.spotify.com/v1/tracks/0uzwUl56ZPCJ... https://api.spotify.com/v1/audio-analysis/0uzw... 194590 4 Polyphia Neurotica 0
10 0.601 0.789 4 -6.822 0 0.0328 0.039500 0.81200 0.1140 0.315 ... audio_features 6AhwAWzSlISc5ZvGonkgdN spotify:track:6AhwAWzSlISc5ZvGonkgdN https://api.spotify.com/v1/tracks/6AhwAWzSlISc... https://api.spotify.com/v1/audio-analysis/6Ahw... 205926 4 Polyphia Playing God 1
11 0.553 0.824 4 -5.261 0 0.0550 0.001850 0.82000 0.3050 0.608 ... audio_features 1W5zSvAeIXlw6odInPE4m5 spotify:track:1W5zSvAeIXlw6odInPE4m5 https://api.spotify.com/v1/tracks/1W5zSvAeIXlw... https://api.spotify.com/v1/audio-analysis/1W5z... 242694 4 Polyphia Reverie 0
12 0.664 0.835 8 -4.858 1 0.0442 0.035900 0.12800 0.1850 0.485 ... audio_features 5aVKIdM550lRzk7rFbPcF7 spotify:track:5aVKIdM550lRzk7rFbPcF7 https://api.spotify.com/v1/tracks/5aVKIdM550lR... https://api.spotify.com/v1/audio-analysis/5aVK... 236653 4 Polyphia Chimera (feat. Lil West) 0
13 0.421 0.931 0 -4.569 1 0.0397 0.000451 0.28700 0.0774 0.393 ... audio_features 0Jf3QiP8WrXEoFsaUsJi0b spotify:track:0Jf3QiP8WrXEoFsaUsJi0b https://api.spotify.com/v1/tracks/0Jf3QiP8WrXE... https://api.spotify.com/v1/audio-analysis/0Jf3... 256333 4 Polyphia Euphoria 0
14 0.640 0.846 9 -5.201 0 0.0298 0.021800 0.54100 0.1160 0.508 ... audio_features 2v7iJcMoQcN40fK9XEb42q spotify:track:2v7iJcMoQcN40fK9XEb42q https://api.spotify.com/v1/tracks/2v7iJcMoQcN4... https://api.spotify.com/v1/audio-analysis/2v7i... 255455 4 Polyphia Goose 0
15 0.600 0.817 4 -4.636 0 0.0488 0.000794 0.00321 0.4620 0.322 ... audio_features 2IMHE3XJcsqTIbSGOIY6Jy spotify:track:2IMHE3XJcsqTIbSGOIY6Jy https://api.spotify.com/v1/tracks/2IMHE3XJcsqT... https://api.spotify.com/v1/audio-analysis/2IMH... 230951 4 Polyphia Bloodbath (feat. Chino Moreno) 0
16 0.681 0.827 6 -4.051 0 0.0651 0.055300 0.00000 0.2990 0.750 ... audio_features 4c8TMfsKJIrRgCWs8LCEbQ spotify:track:4c8TMfsKJIrRgCWs8LCEbQ https://api.spotify.com/v1/tracks/4c8TMfsKJIrR... https://api.spotify.com/v1/audio-analysis/4c8T... 152361 4 Polyphia ABC (feat. Sophia Black) 0
17 0.553 0.885 0 -4.063 1 0.0385 0.000667 0.00072 0.2090 0.484 ... audio_features 6MYzjR2rH0hfz91FsaR1ox spotify:track:6MYzjR2rH0hfz91FsaR1ox https://api.spotify.com/v1/tracks/6MYzjR2rH0hf... https://api.spotify.com/v1/audio-analysis/6MYz... 240006 4 Polyphia So Strange (feat. Cuco) 0
18 0.364 0.423 2 -8.187 1 0.0343 0.001080 0.97500 0.6970 0.187 ... audio_features 4iDH45ZVIdHzDhLYd1FyKF spotify:track:4iDH45ZVIdHzDhLYd1FyKF https://api.spotify.com/v1/tracks/4iDH45ZVIdHz... https://api.spotify.com/v1/audio-analysis/4iDH... 79688 3 Polyphia All Falls Apart 1
19 0.674 0.712 0 -5.338 1 0.0888 0.093500 0.84700 0.1650 0.575 ... audio_features 71N78jUmfhLeRDlLd8elfl spotify:track:71N78jUmfhLeRDlLd8elfl https://api.spotify.com/v1/tracks/71N78jUmfhLe... https://api.spotify.com/v1/audio-analysis/71N7... 194632 4 Polyphia Genesis (feat. Brasstracks) 1

20 rows × 21 columns

Grabar la playlist en mi cuenta¶

In [ ]:
from spotipy.oauth2 import SpotifyOAuth

scope = "playlist-modify-public"
sp = spotipy.Spotify(auth_manager=SpotifyOAuth(scope=scope, client_id=cid, client_secret=secret, redirect_uri='http://localhost:3000'))
In [ ]:
# sp.me()
In [ ]:
user = sp.me()['id']
spotify_playlist = sp.user_playlist_create(user=user, 
                        name='DAD_Playlist')
spotify_playlist_id = spotify_playlist['id']
In [ ]:
## ID del playlist creado ubicado en uri
newPlaylistId = spotify_playlist_id
In [ ]:
## Extraer los track_id de la lista de canciones seleccionadas
trackIDs = list(playlist.track_id)
In [ ]:
## Agregar la lista de canciones al playlist de Spotify
sp.user_playlist_add_tracks(user, newPlaylistId, trackIDs)
Out[ ]:
{'snapshot_id': 'Miw2ZjM5MjQyNmJhMTk1MDczMDVjMmJhNmU1ZWU2NjJiMWE4YTliNDgz'}
In [ ]:
print(f"Lista disponible en: {spotify_playlist['external_urls']['spotify']}")
Lista disponible en: https://open.spotify.com/playlist/74NwjpUzI8VXxi9jsnWVWH